home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1996-04-25 | 14.0 KB | 381 lines | [ TEXT/CWIE]
//================================================================================ // Greg Anderson // db+ // // Abstract base class for DataBase document // 16,17 May 1994 //================================================================================ #pragma once #ifndef __DATABASEDOCUMENT__ #define __DATABASEDOCUMENT__ #include "ReferenceTemplates.h" #include "Int64.h" // // Needed for 'friend' delcaration // #include "AbstractRecord.h" // // For 'Require' // #include "Exceptions.h" class TAbstractBackingStore; class TTransaction; class TGroupControlObject; class TAbstractRecord; class TDBProperty; class TDBElement; enum { kFormatIdentifier = 'db++', // Magic constant // // History of file format revision changes (hopefully not many // of these will ever exist). // // I sort of wish I would have used zero to represent an invalid file // format, and started the first revision with 1. Oh well. // kInitialFileFormatRevision = 0, // Initial release kFileFormatRevisionNumber = kInitialFileFormatRevision, // This is the revision that we will write kEarliestCompatibleRevisionNumber = kFileFormatRevisionNumber, // This is the earliest revision that is forwards-compatable with what we wrote kEarliestWriteCompatibleRevision = kFileFormatRevisionNumber, // This is the earliest revision that we will allow to write over our changes kEarliestInterpretedRevisionNumber = kInitialFileFormatRevision, // This is the earliest revision that we can interpret and convert to the current format kNumberFreeLists = 64, // 64*4 = 256 bytes of free lists kStartOfFreeList = 256, kSizeOfFreeList = 256 }; // // Critical persistant information about the document // is stored in the document file information header // class TDocumentFileInformation { public: TDocumentFileInformation() : fFormatIdentifier(kFormatIdentifier), fEarliestCompatibleRevisionNumber(kEarliestCompatibleRevisionNumber), fFileFormatRevisionNumber(kFileFormatRevisionNumber), fEarliestWriteCompatibleRevision(kEarliestWriteCompatibleRevision), fNumberOfHeaderBytes(sizeof(TDocumentFileInformation)), fStartOfFreeList(kStartOfFreeList), fStartOfRecordData(kStartOfFreeList + kSizeOfFreeList), fNumberOfGroupsOnDisk(0), fMetaRootID(-1) { this->AssignInitialID(); } void AssignInitialID(); Int64 fDocumentID; // A unique ID identifying this document long fFormatIdentifier; // A constant used to verify that this document is indeed in the correct format long fFileFormatRevisionNumber; // The revision number of the file format used to write this document long fEarliestCompatibleRevisionNumber; // The smallest document revision number that is forward-compatibile with the current one (i.e., can an old app ignore the fact that the doc format changed?) long fEarliestWriteCompatibleRevision; // This will probably always == fFileFormatRevisionNumber long fNumberOfHeaderBytes; // Sizeof(TDocumentFileInformation) for this file format revision long fStartOfFreeList; // Beginning of free list (offset from start of file) long fStartOfRecordData; // Beginning of database record data (offset from start of file) long fNumberOfGroupsOnDisk; // How many record groups have been created and written to disk long fMetaRootID; // The Meta-root is almost always (always?) at node ID zero, but this record identifies it }; //================================================================================ // Class TDatabaseDocument //================================================================================ class TDatabaseDocument { // // Group control objects are friends of DBDocuments // because they need to be able to change the free list // pointers when a record is pushed onto a free list // friend class TGroupControlObject; // MakeRecord, ReadRecordRange, WriteRecordRange, RemoveFromFreeList // // Abstract records are allowed specific access to // a couple of private routines // friend void TAbstractRecord::CacheGroupControlObject(); // GetGroupControlObject friend void TAbstractRecord::PushRecordIfFree(TTransaction* transaction); // PushFreeRecordOntoFreeList // // ----- Fields -------------------------------------------------------------- // private: // // Transient fields (not written to disk) // TGroupControlObject** fRecordGroupList; // A pointer to an array of pointers to group control objects long fNumberOfGroups; // Number of record groups that may be loaded into memory TAbstractBackingStore* fBackingStore; // Object in charge of reading/writing // // Persistent fields (written to disk) // TDocumentFileInformation fDocumentInformation; long fFreeList[kNumberFreeLists]; // // ----- Methods ------------------------------------------------------------- // public: TDatabaseDocument() : fRecordGroupList(nil), fNumberOfGroups(0), fBackingStore(nil), fDocumentInformation() { // // Clear out the free list // for(short i=0; i<kNumberFreeLists; ++i) fFreeList[i] = kNilIndex; // // Create the meta-root, but don't use // a transaction; this is just an initialization // step, and if it fails the entire document // is unusable. // this->CreateMetaRoot(); }; TDatabaseDocument(TAbstractBackingStore* backingStore) : fRecordGroupList(nil), fNumberOfGroups(0), fBackingStore(backingStore), fDocumentInformation() { this->ReadDocumentInformation(); }; virtual ~TDatabaseDocument(); //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Methods of TTransactionAwareObject: //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // // A documents key space is its document identifier; its key is // unused, since the key space identifies the document. We need // to make sure that the key never interferes with keys used by // records of the document, so we assign -1 (kNilIndex) to be // the key for the document itself. // virtual Int64 ObjectsKeySpace() const; virtual long ObjectKey() const { return -1; }; //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Public interface: //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: public: // // Get the record that contains all of the trees in the // database (the meta-root). // // A document must always have a Meta-Root. // // The properties of the document are stored as properties // of the meta-root. A document must always have a meta-root. // It may further contain 0-N trees; the tree roots are stored // as elements of the metaroot. // AConst<TDBElement> GetMetaRoot() { AConst<TAbstractRecord> metaRoot = this->GetRecordCursor(this->MetaRootIndex()); Require(metaRoot.Exists()); return metaRoot->DBElementCursor(); } // // The methods for creating new records are not protected, but they // still require a transaction in order to be called. The newly-created // cursor is always added to the transaction provided, and will be // returned to the free list if the transaction's changes are discarded. // // n.b. New properites and elements are not properties or elements of // any particular record until they are explicitly added to some // record in the database. // AnUpdate<TDBProperty> NewDBProperty(TTransaction* transaction); AnUpdate<TDBElement> NewDBElement(TTransaction* transaction); // // Perhaps this will be protected later; only DBProperties should ever // create a data record. // AnUpdate<TDataRecord> NewDataRecord(TTransaction* transaction, long sizeOfData); //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Less common methods: //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // // Most clients of the database document won't need to call // "GetRecordCursor" directly, but it is legal to cache a // record index and later recover a cursor to that element, // if desired. // // Usually, though, all navagation will start from the meta-root. // AConst<TAbstractRecord> GetRecordCursor(long recordIndex) const; // // Without a backing-store object, the database object must remain // RAM-resident for its entire existance; it cannot be saved, reloaded // or partially paged out. Setting the backing store object // will also save the database to disk immediately, OVERWRITING the // previous contents of the backing store with the current contents // of the database. To reload an existing database, the backing // store must be provided to the database's constructor. // // This API allows an application to make a RAM-based database and // associate it with a file later, if and only if it needs to save // the database's contents. Of course, portions of the database // cannot be paged out unless it has a file to write into. // // Once the backing store is set, the database document owns it, and // will delete it in its destructor. // void SetBackingStore(TAbstractBackingStore* backingStore); // // Usually, a client should not need to look at the backing store // object of a document; if you do call this method, though, remember // that the document owns the backing store, and will delete it // in its destructor. // TAbstractBackingStore* GetBackingStore() const { return fBackingStore; } // // Save a copy of this database in some other file. // void SaveACopy(TAbstractBackingStore* backingStore); // // Normally, the database is lazy about writing changes back to // disk. If you wish, all changes can be flushed back to disk // on demand. // void FlushChangesToDisk(); // // This method returns true if the document has had changes made // to it that have not yet been written out to disk. Will return // true even if the document has no backing store object. // Boolean DocumentNeedsSave(); // // Someone might care if the document can be saved to disk or not. // This method returns true if and only if this document has a backing // store object that has a valid, open file // Boolean CanSaveDocument(); // // This method asks the backing store what the name of this // document is // void DocumentName(TUpdataDataReference& name); // // Some clients need to identify the metaroot. Perhaps exporting // this index is a very bad way to identify the metaroot. At the // very least, this should probably be a protected method, as it // doesn't really need to be part of TDatabaseDocument's exported API. // long MetaRootIndex() { return fDocumentInformation.fMetaRootID; } //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Protected methods: //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: protected: TGroupControlObject* GetGroupControlObject(long recordIndex) const; TAbstractRecord* GetRecord(long recordIndex) const; TAbstractRecord* MakeRecord(long recordIndex, long recordIDWord); virtual void ReadRecordRange(void* bufferStart, long byteOffsetToRecord, long numberOfBytes, TAbstractBackingStore* backingStoreToUse = nil); virtual void WriteRecordRange(void* bufferStart, long byteOffsetToRecord, long numberOfBytes, TAbstractBackingStore* backingStoreToUse = nil); virtual long GetFirstFreeIndex(long whichFreeList) const; virtual void SetFreeIndex(long whichFreeList, long firstFree); long GetNextFreeIndex(long afterWhichFreeIndex) const; long GetPreviousFreeIndex(long beforeWhichIndex) const; long PopIndexFromFreeList(long whichFreeList); void PushFreeRecordOntoFreeList(long recordToFree); void RemoveFromFreeList(long recordToRemove); void VerifyFreeLists() const; //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Private methods: //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: private: long InitializeNewGroup(TGroupControlObject* group); long InitializeNewDataGroup(TGroupControlObject* group, long desiredEncodedPhysicalSize); void CreateGroupList(long numberOfGroups); void CacheGroupControlObject(TGroupControlObject* newGroup); TGroupControlObject* CreateGroupControlObject(long firstRecord); long MakeMoreFreeRecords(long addToWhichFreeList); void WriteDocumentInformation(TAbstractBackingStore* backingStoreToUse = nil); void ReadDocumentInformation(); void CreateMetaRoot(); }; //================================================================================ // Class TAbstractBackingStore // // A backing-store object is used to save, reload and page out a database //================================================================================ class TAbstractBackingStore { protected: Boolean fCanWrite; public: TAbstractBackingStore() : fCanWrite(true) {} virtual ~TAbstractBackingStore(); // // For downcast-tests // virtual long BackingStoreType() const = 0; // // CanSaveDocument should return 'true' if this backing store object // has a valid, open file (or equivalent) to save the database into // AND the fCanWrite boolean has not been clear // virtual Boolean CanSaveDocument(); void SetWriteEnable(Boolean canWrite) { fCanWrite = canWrite; } // // Read/Write are passed: // // bufferStart - a memory buffer to write from / read into // startByteInFile - the offset from the beginning of the database to begin reading from // numberOfBytes - the number of bytes to read/write // virtual void Read(void* bufferStart, Int64 startByteInFile, long numberOfBytes) = 0; virtual void Write(void* bufferStart, Int64 startByteInFile, long numberOfBytes) = 0; // // Return the name of the file associated with this backing store // virtual void DocumentName(TUpdataDataReference& name) = 0; }; #endif